/**
 * Plugin that takes a JSON array input and matches a list to that array
 *
 * @author Diwant Vaidya
 * @date April 6, 2010
 * 
 */
(function($) {

var currentThings = new Array();
var container;
var prefix;
var getSortId;
var template;
var childrenTemplate;
var childrenProperty;

$.fn.domListReflectJSONArray = function(jsonArray, options) {
	if( !(jsonArray instanceof Array) ){
		debug("JSON Array is not an array!");
		return this;
	}
	// Extend our default options with those provided.
	var opts = $.extend($.fn.domListReflectJSONArray.defaults, options);
	jsonArray.sort(function(a,b){
		var sA = opts.getSortId(a);
		var sB = opts.getSortId(b);
		if( parseInt(sA).toString() == sA ){
			sA = parseInt(sA);
			sB = parseInt(sB);
		}
		if(opts.ascending){
			return (sA<sB)?-1:1;
		} else {
			return (sA<sB)?1:-1;
		}
	});
	this.each(function() {
		// element-specific code here
		var $this = $(this);
		debug("Now doing ["+$this.attr('id')+" - "+$this.attr('class')+"]");
		// build element specific options
		var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
		container = $this;
		var theTemplate = container.children(".template").clone();
		theTemplate.children(".template").remove();
		template = theTemplate.html();
		theTemplate.remove();
		var theChildTemplate = container.children(".template").children(".template");
		childrenTemplate = ( theChildTemplate.length>0 )?theChildTemplate.html():"";
		currentThings = new Array();
		prefix = o.prefix;
		getSortId = o.getSortId;
		childrenProperty = o.childrenProperty;
		updateCurrentThings();
		updateAddRemove(jsonArray);
		for(var i in jsonArray){
			var eachItem = jsonArray[i];
			console.debug("children on ",eachItem);
			updateChildren(eachItem,o);
		}
	});
	return this;
};
function updateChildren(eachItem, options){
	var itemDomId = getDOMId(getSortId(eachItem));
	if( eachItem[childrenProperty] && childrenTemplate.length>0){
		console.debug("==",eachItem[childrenProperty].length, eachItem[childrenProperty], eachItem);
		var childrenList = $("#"+itemDomId+"-children");
		if( childrenList.length == 0){
			childrenList = $("<ul id='"+itemDomId+"-children' class='isAChild'><li class='template' style='display:none;'>"+childrenTemplate+"</li></ul>").appendTo($("#"+itemDomId)).end();
		} 
		console.debug("==",childrenList);
		var tempChildTemplate = childrenTemplate;
		var tempContainer = container;
		childrenList.domListReflectJSONArray(eachItem[childrenProperty], options);
		childrenTemplate = tempChildTemplate;
		container = tempContainer;
	} else {
		console.debug("==",0,"no property found!",childrenProperty, childrenTemplate);
		var childrenList = $("#"+itemDomId+"-children");
		if( childrenList.length!=0 ){
			childrenList.domListReflectJSONArray(new Array(),options);
		}
	}
}
function updateCurrentThings(){
	container.children("li").filter(":not(.template)").each(function(){
		var matches = template.match(/#data([A-z0-9]+)/g);
		var details = $(this).children(".details");
		var theCurrentThing = new Object();
		for(var i in matches){
			var theProperty = matches[i];
			theClass = theProperty.substr(1);
			theProperty = theProperty.substr(5);
			theCurrentThing[theProperty] = details.children("."+theClass+":first").html();
		}
		var indexOfCurrent = findThingGivenSortId( getSortId(theCurrentThing) );
		if( indexOfCurrent==-1 ){
			currentThings.push(theCurrentThing);
		}	
	});		
}
function updateAddRemove( newThings ) {
	// lets get the ids of the things removed.
	var newIds = new Array();
	var currentIds = new Array();
	var removedIds = new Array();
	var movedIds = new Array();
	// Lets get an array with the new ids
	for( var i in newThings ){
		newIds.push(getSortId(newThings[i]));
	}
	// An array with old ids
	for( var i in currentThings ){
		currentIds.push(getSortId(currentThings[i]));
	}
	for( var i in currentIds ){
		var id = currentIds[i];
		if( $.inArray(id, newIds)==-1 ){
			removedIds.push(id);
		}
	}
	debug("currentIds ["+currentIds+"]");
	debug("newIds     ["+newIds+"]");
	debug("removedIds ["+removedIds+"]");

	// remove the removed things
	for( var i in removedIds ){
		var id = removedIds[i];
		removeAThing(id);
	}

	// add and move
	var addAfter = null;
	for( var i in newIds){
		var id = newIds[i];
		if( $.inArray(id, currentIds)==-1){
			// does not exist in current set
			addAThing(newThings[i], addAfter);
			debug("added "+id+" after "+addAfter);
			addAfter = id;
		} else {
			// figure out the first id in new one that is before this and was in the current set of ids
			var idIndexInNew = $.inArray(id, newIds);
			var lastNonNewIndex= idIndexInNew-1;
			while( lastNonNewIndex>=0 ){
				if( ($.inArray(newIds[lastNonNewIndex], newIds)!=-1)){
					break;
				}
				lastNonNewIndex-=1;
			}
			var idBeforeThisInCurrent = null;
			var idIndexInCurrent= $.inArray(id, currentIds);
			if( idIndexInCurrent!=0 ){
				idBeforeThisInCurrent = currentIds[idIndexInCurrent-1];
			}
			var idBeforeThisInNew = null;
			if( lastNonNewIndex!=-1){
				idBeforeThisInNew = newIds[lastNonNewIndex];
			}
			if( (idBeforeThisInNew==null) && (idBeforeThisInCurrent==null)){
				// no move 
				debug(id+" did not need to be moved:  it used to be the top thing and it is still the top non new thing");
			}  else if( (idBeforeThisInNew==null) ){
				debug(id+" does not need to be moved.  It used to be after "+idBeforeThisInCurrent+" but now it is the first thing.  This means everything before it will be removed or moved.");
			} else if( ( idBeforeThisInCurrent==null ) || (idBeforeThisInCurrent!=idBeforeThisInNew) ){
				// a move is required to the after the thing before this in the newIds column.
				debug(id+" had to be moved.  It used to be after "+idBeforeThisInCurrent+" but now it is after "+idBeforeThisInNew+".  Moving to after "+newIds[idIndexInNew-1]);
				movedIds.push(id);
				moveAThing(id, newIds[idIndexInNew-1]);
			} else {
				if( $.inArray(id, movedIds) ){
					debug(id +" is still after "+idBeforeThisInNew +" but since that was moved, this must be moved too");
					movedIds.push(id);
					moveAThing(id, newIds[idIndexInNew-1]);
				} else {
					debug(id+" did not need to be moved: it still is after "+idBeforeThisInNew+" as it was after the same ("+idBeforeThisInCurrent+") before");
				}
			}			
			addAfter=id;
		}
	}
};
function getDOMId( sortId ){
	var containerId = container.attr('id');
	if( containerId.length==0 ){
		containerId="i";
	}
	return containerId+"-"+prefix+"-"+sortId.toString();
};
function findThingGivenSortId( sortId ){
	for( var i in currentThings ){
		if( getSortId(currentThings[i])==sortId ){
			return parseInt(i);
		}
	}
	return -1;
};
function addAThing( newThingData, addAfterId ){
	if( $("#"+getDOMId(getSortId(newThingData))).length>0 ){
		$("#"+getDOMId(getSortId(newThingData))).fadeIn();
	} else {
		var toAdd = "<li id="+getDOMId(getSortId(newThingData))+">"+getDOMHtml(newThingData)+"</li>";
		if( addAfterId==null ){
			container.append(toAdd);
		} else {
			$("#"+getDOMId(addAfterId)).after(toAdd);
		}
		$("#"+getDOMId(getSortId(newThingData))).hide().fadeIn();
		currentThings.push( newThingData );
	}
};
function removeAThing ( sortIdOfThingToRemove ){
	debug(sortIdOfThingToRemove);
	var indexOfRemove = findThingGivenSortId( sortIdOfThingToRemove );
	if( indexOfRemove>=0 ){
		currentThings.splice(indexOfRemove, 1);
	}	
	$("#"+getDOMId(sortIdOfThingToRemove)).hide("fast",function(){$(this).remove()});
};
function moveAThing ( sortIdToMove, idToMoveAfter ){
	debug("Moving "+sortIdToMove+" after "+idToMoveAfter);
	var indexOfRemove = findThingGivenSortId( sortIdToMove );
	var theDataToMove = null;
	if( indexOfRemove>=0 ){
		theDataToMove = currentThings[indexOfRemove];
		currentThings.splice(indexOfRemove, 1);
	}	
	var indexOfAfter = findThingGivenSortId( idToMoveAfter );
	indexOfAfter+=1;
	if( indexOfAfter>=0 ){
		currentThings.splice(indexOfAfter, 0,theDataToMove);
	} else {
		currentThings.push(theDataToMove);
	}
	$("#"+getDOMId(sortIdToMove)).hide();
	$("#"+getDOMId(idToMoveAfter)).after($("#"+getDOMId(sortIdToMove)));
	$("#"+getDOMId(sortIdToMove)).show();
};
function getDOMHtml(data){
	var newHtml = template.toString();
	var dataPlaces = /#data([A-z0-9]+)/g;
	var matches = newHtml.match(dataPlaces);
	for(var i in matches){
		var toReplace = matches[i];
		var theProperty = toReplace.substr(5);
		newHtml = newHtml.replace(toReplace, data[theProperty]);
	}
	return newHtml;
}
// private function for debugging
function debug(msg) {
	return; // debugging is turned off
	if (window.console && window.console.log) {
		window.console.log('[domListReflectJSONArray] ' + msg);
	}
};
// plugin defaults - added as a property on our plugin function
$.fn.domListReflectJSONArray.defaults = {
	prefix: "defaultPrefix"+(Math.floor(Math.random()*100)).toString(),
	getSortId: function( thing ){return thing.id.toString()},
	ascending:true,
	childrenProperty:"children"
};

})(jQuery);

